/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: Mobile.java 49 2008-05-17 02:35:15Z jens464 $
*/
package albareth.core.entity;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import albareth.core.Game;
import albareth.core.GameEffect;
import albareth.core.GameMap;
import albareth.core.action.Action;
import albareth.core.action.Spell;
import albareth.core.brain.Brain;
import albareth.core.buff.Buff;
import albareth.core.entity.item.BasicItemContainer;
import albareth.frontend.Resources;
import albareth.frontend.Tile;
import albareth.util.Location;
import frosch.util.Bresenham;
import frosch.util.Direction;
import frosch.util.FToolkit;
import frosch.util.MutableRange;
import frosch.util.Range;
import frosch.util.glider.Easing;

public class Mobile extends Entity implements ItemContainer
{
    public static final int INVENTORY_CAPACITY = 40;
    
    public static final int STUMBLE_DURATION = 1500;
    
    public static final int FLAG_FROZEN     = 0x01;
    public static final int FLAG_STONE      = 0x02;
    public static final int FLAG_INVISIBLE  = 0x04;
    public static final int FLAG_DEAD       = 0x08;
    public static final int FLAG_FEARED     = 0x10;
    public static final int FLAG_INVUL      = 0x20;
    
    public static final int COOLDOWN_GROUP_POTIONS = 0;
    public static final int COOLDOWN_GROUP_FOOD    = 1;
    //public static final int COOLDOWN_GROUP_SCROLLS = 2;

    //-1 means: does not attack
    public static final int DEFAULT_ATTACK_SPEED = 2500;
    public static final int UNARMED_ATTACK_SPEED = 1000;
    public static final int DEFAULT_ARMOR_CLASS = 10;
    public static final Range DEFAULT_DAMAGE = Range.NULLRANGE;





    private int _flags = 0;
    
    private long[] _groupCooldownEndTime = new long[2];
    private static int[] _groupCooldownDuration =
        new int[]
                 {
            10000, //COOLDOWN_GROUP_POTIONS
            10000, //COOLDOWN_GROUP_FOOD
            //10000 //COOLDOWN_GROUP_SCROLLS
                 };

    private long _meleeCooldownEndTime;

    private long _walkCooldownEndTime;
    private int _lastWalkDirection = Direction.NOWHERE;

    private Action _currentAction = null;
    private ArrayList _spells = new ArrayList();

    
    //game time, this mob last took damage
    private long _lastDamageTime = -100000;
    private long _lastMeleeAttackTime = -100000;
    private long _lastStumbleTime = -100000;
    
    private ArrayList _buffs = null;
    
    private BasicItemContainer _backpack =
        new BasicItemContainer("", null)
    {
    };

    private Brain _brain = null;
    private boolean _isEvil = true;

    private int _level = 1;
    private int _hitpoints = 0;
    private int _maxHitpoints = 0;
    private int _mana = 0;
    private int _maxMana = 0;

    private MutableRange _baseMeleeDamage = new MutableRange(DEFAULT_DAMAGE);
    private int _baseMeleeAttackSpeed = DEFAULT_ATTACK_SPEED;
    private int _baseArmorClass = 10;
    private int _baseThac0 = 20;
    private int _meleeAttackSpeedMod = 0;
    private int _thac0Mod = 0;
    private int _armorClassMod = 0;
    private int _meleeDamageMod = 0;
    
    Location _home = null;
    int _maxHomeDist = 1;
    
    Location _targetLocation = null;
    Mobile _targetMobile = null;

    private boolean _boss = false;


    
    public Mobile(String name, Tile tile)
    {
        super(name, tile);
        _backpack.setOwner(this);
        _backpack.setCapacity(INVENTORY_CAPACITY);
    }
    
    public Mobile(String name, Tile tile, int hits, int mana)
    {
        this(name, tile);
        _maxHitpoints = _hitpoints = hits;
        _maxMana = _mana = mana;
    }
    
    public Object clone() throws CloneNotSupportedException
    {
        throw new CloneNotSupportedException("Cannot clone mobiles yet.");
    }
    
    public boolean isMobile()
    {
        return true;
    }
    
    public Brain getBrain()
    {
        return _brain;
    }
    
    public MobileController getMobileController()
    {
        return _brain;
    }

    public void setBrain(Brain brain)
    {
        brain.setControlledMobile(this);
        _brain = brain;
    }
    
    public boolean tryPickup(Item item)
    {
        if (canPickUpItem(item))
        {
            boolean success = addItem(item);
            return success;
        }
        return false;
    }
    
    public boolean canPickUpItem(Item item)
    {
        return (
                item.getOwner() != getBackpack() &&
                numItems() < _backpack.getCapacity() &&
                !item.isLockedDown() &&
                item.getLocation().distance(getLocation()) < 2
        );
    }
    


    
    public void dropItemAt(Item item, Location location)
    {
        if (canDropItemAt(item, location)) {
                removeItem(item);
                item.moveTo(location);
        }
    }
    
    public boolean canDropItemAt(Item item, Location location)
    {
        return containsItem(item) && !location.isBlocked() &&
               location.distance(getLocation()) <= 1;
    }

    public boolean canDropItemIn(Item item, ItemContainer container)
    {
        return !container.containsItem(item) &&
               !(container.getLocation().distance(getLocation()) > 1);
    }

    public ItemContainer getBackpack()
    {
        return _backpack;
    }
    
    public boolean addItem(Item item)
    {
        return _backpack.addItem(item);
    }
    
    public boolean addAllItems(List items)
    {
        return _backpack.addAllItems(items);
    }


    public boolean containsItem(Item item)
    {
        return _backpack.containsItem(item);
    }

    public void dropAllItems()
    {
        _backpack.dropAllItems();
    }

    public boolean dropItem(Item item)
    {
        return _backpack.dropItem(item);
    }
    
    public Item dropItem(Item item, int amount)
    {
        return _backpack.dropItem(item, amount);
    }


    public Item dropItem(int i)
    {
        return _backpack.dropItem(i);
    }

    public List getAllItems()
    {
        return _backpack.getAllItems();
    }

    public void removeAllItems()
    {
        _backpack.removeAllItems();
    }

    public boolean removeItem(Item item)
    {
        return _backpack.removeItem(item);
    }

    public Item removeItem(int index)
    {
        return _backpack.removeItem(index);
    }
    
    public int numItems()
    {
        return _backpack.numItems();
    }

    

    

    
    public int getLevel()
    {
        return _level;
    }
    
    public void setLevel(int level)
    {
        _level = level;
    }

    public boolean isBoss()
    {
        return _boss;
    }

    public void setBoss(boolean b)
    {
        _boss = b;
    }
    

    public int getXPGranted()
    {
        return _level * 3 * (isBoss() ? 3 : 1);
    }

    
    public boolean isPlayerControlled()
    {
        return (_brain == null);
    }

    public int getHitpoints()
    {
        return _hitpoints;
    }

    public void setHitpoints(int hitpoints)
    {
        _hitpoints = hitpoints;
    }
    
    public int getMaxHitpoints()
    {
        return _maxHitpoints;
    }
    
    public void setMaxHitpoints(int maxHitpoints)
    {
        _maxHitpoints = maxHitpoints;
    }

    public int getMana()
    {
        return _mana;
    }

    public void setMana(int mana)
    {
        _mana = mana;
    }
    
    public void consumeMana(int mana)
    {
        _mana -= mana;
    }
    
    public int getMaxMana()
    {
        return _maxMana;
    }
    
    public void setMaxMana(int maxMana)
    {
        _maxMana = maxMana;
    }

    public void setFlags(int flags)
    {
        _flags |= flags;
    }
    
    public void unsetFlags(int flags)
    {
        _flags &= ~flags;
    }
    
    public boolean isFlagSet(int flags)
    {
        return (_flags & flags) > 0;
    }
    
    public boolean allFlagsSet(int flags)
    {
        return (_flags & flags) == flags;
    }
    
    public int getFlags()
    {
        return _flags;
    }
    
    /*
    public boolean isEngaged()
    {
        return (_autoAttack != null && !_autoAttack.isAborted());
    }*/

    public void setEvil(boolean b)
    {
        _isEvil = b;
    }
    
    public boolean isEvil()
    {
        return _isEvil ;
    }

    public void addSpell(Spell spell)
    {
        spell.setPerformer(this);
        _spells.add(spell);
    }
    
    public Spell getSpell(int i)
    {
        return (Spell)_spells.get(i);
    }
    
    public List getSpells()
    {
        return _spells;
    }

    public final int takeDamage(int rawDamage, int damageType, Entity causer)
    {
        return takeDamage(rawDamage, damageType, causer, true);
    }
    
    public int takeDamage(int rawDamage, int damageType, Entity causer, boolean canCrit)
    {
        //critical damage?
        if (canCrit && isStumbling() ||
                (causer.isMobile() && ((Mobile)causer).isPlayerControlled() &&
                        FToolkit.rand(20) == 0))
        {
            rawDamage *= 2;
            
            emitOverheadMessage(
                    //TODO: different comic emotes in color of damage type
                    String.valueOf("CRIT!"),
                    isPlayerControlled() ?
                            Resources.COLOR_RED : Resources.COLOR_YELLOW,
                            Easing.EASING_BACK
            );
            
            if (isPlayerControlled()) {
                emitSound(Resources.SOUND_OUCH);
            } else {
                emitSound(Resources.SOUND_MELEE_CRITICAL_HIT);
            }
        }

        int realDamage = isFlagSet(FLAG_INVUL) ? 0 : rawDamage;

        emitOverheadMessage(
                String.valueOf(realDamage),
                isPlayerControlled() ?
                        Resources.COLOR_RED : Resources.COLOR_YELLOW,
                Easing.EASING_ELASTIC
        );
        
        _hitpoints -= realDamage;
        
        if (realDamage > 0) {
            _lastDamageTime = Game.getContext().getPreciseTime();
        }
        
        if (_hitpoints < 1) {
            processDeath(causer);

        } else if (_brain != null && _hitpoints <= (_maxHitpoints / 7))
        {
            _brain.notifyNearDeath();
        }
        
        return realDamage;
    }
    
    /**
     * Call death trigger, spawn blood and loot, xp and stuff.
     * TODO: drop loot and do other stuff.
     * 
     * @param causer
     */
    private void processDeath(Entity causer)
    {
        if (onDeath(causer) > 0) { //escaped death by some scripted event?
            return;
        }
        
        emitEffect(
                GameEffect.stationary(
                        Resources.EFFECT_DEBRIS, getLocation(), 1000
                )
        );

        //TODO: blood decay timer
        Item blood = ItemFactory.createBlood();
        blood.moveTo(getLocation());
        
        if (causer.isMobile()) {
            ((Mobile)causer).processKill(this);
        }
        
        //drop loot when npc:
        if (!isPlayerControlled())
        {
            dropAllItems();
        }
        
        setFlags(FLAG_DEAD);
        getMap().removeEntity(this);
        
    }
    
    public boolean isStumbling()
    {
        return getTimeSinceLastStumble() < STUMBLE_DURATION; 
    }

    public long getTimeSinceLastDamage()
    {
        return Game.getContext().getPreciseTime() - _lastDamageTime;
    }

    public long getTimeSinceLastMeleeAttack()
    {
        return Game.getContext().getPreciseTime() - _lastMeleeAttackTime;
    }

    public long getTimeSinceLastStumble()
    {
        return Game.getContext().getPreciseTime() - _lastStumbleTime;
    }

    /*
    public void activateGlobalCooldown(long duration)
    {
        _globalCooldownDuration = duration;
        _globalCooldownEndTime = Game.getContext().getPreciseTime() + duration;
    }
    */
    
    public void activateGroupCooldown(int group)
    {
        _groupCooldownEndTime[group] = Game.getContext().getPreciseTime() +
            _groupCooldownDuration[group];
    }

    public final long getGroupCooldownDurationLeft(int group)
    {
        return group < 0 ? 0 :
            _groupCooldownEndTime[group] -  Game.getContext().getPreciseTime();
    }
    
    public final int getGroupCooldownDuration(int group)
    {
        return group < 0 ? 0 : _groupCooldownDuration[group];
    }

    public boolean isGroupCooldownActive(int group)
    {
        if (group < 0) {
            return false;
        }
        return getGroupCooldownDurationLeft(group) > 0;
    }
    
    public final int getCooldownDuration(Activatable activatable)
    {
        int individual = activatable.getCooldownDuration();
        int group = getGroupCooldownDuration(activatable.getCooldownGroup());
        return individual > group ? individual : group;

    }
    
    public final long getCooldownDurationLeft(Activatable activatable)
    {
        long individual = activatable.getCooldownDurationLeft();
        long group = getGroupCooldownDurationLeft(activatable.getCooldownGroup());
        return individual > group ? individual : group;
    }


    //TODO: meele cooldown as a cooldown group?
    public void activateMeleeCooldown()
    {
        _lastMeleeAttackTime = Game.getContext().getPreciseTime();
        _meleeCooldownEndTime = Game.getContext().getPreciseTime() + getBaseMeleeAttackSpeed();
    }
    
    public void  activateWalkCooldown()
    {
        _walkCooldownEndTime = Game.getContext().getPreciseTime() + getWalkCooldown();
    }

    public final long getMeleeCooldownTimeLeft()
    {
        return _meleeCooldownEndTime - Game.getContext().getPreciseTime();
    }


    public boolean isWalkCooldownActive()
    {
        return _walkCooldownEndTime > Game.getContext().getPreciseTime();
    }
    
    public final long getWalkCooldownTimeLeft()
    {
        return _walkCooldownEndTime - Game.getContext().getPreciseTime();
    }
    
    public boolean isMeleeCooldownActive()
    {
        return _meleeCooldownEndTime > Game.getContext().getPreciseTime();
    }
    
    /*
    public boolean isActionAvailable(int i, long currentTime)
    {
        Action action = (Action)_actions.get(i);
        
        if (action == null || !action.isAvailable()) {
            return false;
        }
         
        return true;
    }*/
    
    public final boolean hasLineOfSight(Location to)
    {
        return hasLineOfSight(to.x, to.y, to.map);
    }
    
    public boolean hasLineOfSight(int x, int y, GameMap map)
    {
        GameMap thisMap = getMap();
        if (map != thisMap) {
            return false;
        }
        Bresenham bresen = new Bresenham(getX(), getY(), x, y);
        for (;;)
        {
            bresen.iterate();
            if (!map.isTranslucent(bresen.x, bresen.y)) {
                return false;
            }
            if (bresen.x == x && bresen.y == y) {
                return true;
            }
        }
    }
    
    public void update()
    {
        if (_brain != null) {
            _brain.act();
        }
    }

    public Action getActiveAction()
    {
        if (_currentAction != null && _currentAction.isActivated()) {
            return _currentAction;
        } else {
            return null;
        }
    }
    
    public boolean canUseItem(Item item)
    {
        return (
                item.isActivatable() && 
                (item.getOwner() == getBackpack() || 
                 item.getLocation().distance(getLocation()) < 2)
        );

    }

    public boolean tryAction(Action action)
    {
        if (isGroupCooldownActive(action.getCooldownGroup())) {
            return false;
        }

        action.setPerformer(this);

        //try to initiate the action asked for.
        boolean success = action.initiate();
        if (success)
        {
            abortActiveAction();

            long delay = action.getCastingDelay();
            if (delay == 0) { //instant casting action?
                action.execute();
            } else {
                _currentAction = action;
                Game.getContext().scheduleTask(action, delay);
            }
        }
        
        return success;
    }
    
    private void abortActiveAction()
    {
        Action activeAction = getActiveAction();
        if (activeAction != null) {
            activeAction.abort();
        }
    }
    
    public boolean tryBuff(Buff buff)
    {
        //am i immune?
        if (!buff.isBeneficial() && buff.isImmune(this)) {
            return false;
        }
        
        //already there and now stack it?
        //TODO: implement equals in Buff and subclasses for correct stacking
        int buffIdx;
        if (_buffs != null && (buffIdx = _buffs.indexOf(buff)) > -1)
        {
            Buff oldBuff = (Buff)_buffs.get(buffIdx);
            if (oldBuff.getStack() >= oldBuff.getMaxStack()) {
                return false; //max stacksize already reached
            }
            oldBuff.increaseStack();
            return true;
        }
        
        //if not, equip buff:
        if (_buffs == null) {
            _buffs = new ArrayList();
        }
        buff.setPerformer(this);
        _buffs.add(buff);
        buff.initiate();
        Game.getContext().scheduleTask(buff, buff.getPeriod(), buff.getPeriod());
        return true;
    }

    public void fadeBuff(Buff buff)
    {
        if (_buffs != null && _buffs.contains(buff))
        {
            _buffs.remove(buff);
            buff.abort();
        }
    }

    /**
     * 
     * @param direction
     * @return whether the walking succeeded.
     */
    public boolean tryWalk(int direction)
    {
        if (!canWalk()) {
            return false;
        }
        
        Location targetLoc = getLocation().getNeighborLocation(direction);

        //overrideable hook. return 1 cancels
        if (onWalk(targetLoc) == 1) {
            return false;
        }

        if (targetLoc.isBlocked()) {
            return false;
        }
        
        
        abortActiveAction();
        
        moveTo(targetLoc);
        
        _lastWalkDirection = direction;
        
        activateWalkCooldown();
        //_walkCooldownEndTime = Game.getContext().getPreciseTime() + getWalkCooldown();

        return true;
    }
    
    
    public void moveTo(GameMap map, int x, int y)
    {
        super.moveTo(map, x, y);

        //generate step events on new location's items:
        List items = map.getItems(x, y);
        if (items != null)
        {
            for (Iterator it = items.iterator(); it.hasNext(); )
            {
                Item item = (Item)it.next();
                item.steppedOn(this);
            }
        }
    }

    public boolean tryMeleeAttack(Mobile targetMobile)
    {
        if (!canMelee()) {
            return false; //unable to attack
        }
        
        if (targetMobile == null) {
            return false; //we have no target
        }
        
        if (getDistance(targetMobile) > 1) {
            return false; //out of range
        }
        
        abortActiveAction();
        

        int d20 = FToolkit.rand(1, 20);
        
        if (hitSucceeded(d20, targetMobile))
        {
            //TODO: call mobile triggers onHit, onGetHit. weapon triggers onHit
            
            int damage = FToolkit.rand(
                    getMeleeDamageMin(), getMeleeDamageMax());
            
            targetMobile.takeDamage(damage, 0, this); //TODO: damage flags

            if (targetMobile.getBrain() != null) {
                targetMobile.getBrain().notifyAttacked(this);
            }
            
            emitSound(Resources.SOUND_MELEE_HIT);
            
        } else {
            //npcs stumble on miss roll 1:
            processMeleeMiss(d20);
            emitSound(Resources.SOUND_MELEE_MISS);
        }
        
        activateMeleeCooldown();
        
        return true;
    }

    protected boolean hitSucceeded(int roll, Mobile targetMobile)
    {
        int toHit = getTHAC0() - targetMobile.getArmorClass();
        return (roll > toHit); 
    }

    protected void processMeleeMiss(int hitRoll)
    {
        if (hitRoll <= 2) {
            activateStumble();
            emitSound(Resources.SOUND_NPC_STUMBLING);
        }
    }

    protected final void activateStumble()
    {
        _lastStumbleTime = Game.getContext().getPreciseTime();
        emitOverheadMessage(
                String.valueOf("STUMBLE!"),
                isPlayerControlled() ?
                        Resources.COLOR_RED : Resources.COLOR_YELLOW,
                        Easing.EASING_NONE
        );

    }

    /**
     * Tries to find and attack a destroyable item.
     */
    public boolean tryItemAttack(Location targetLocation)
    {
        List items = targetLocation.getItems();
        for (Iterator it = items.iterator(); it.hasNext(); )
        {
            Item item = (Item)it.next();
            if (item.isDestroyable())
            {
                int damage = FToolkit.rand(
                        getMeleeDamageMin(), getMeleeDamageMax());
                item.takeDamage(damage, 0, this);
                activateMeleeCooldown();
                return true;
            }
        }
        return false;
    }


    public int getWalkCooldown()
    {
        return 1500;
    }
    
    /*
    public int getAttackSpeed()
    {
        return 1000;
    }
    */
    
    public boolean isHostileTo(Mobile other)
    {
        //the evil guys work together.
        return isEvil() != other.isEvil();
    }


    /* begin messenging stuff */

    public void infoMsg(String message)
    {
    }
    
    public void warnMsg(String message)
    {
    }
    
    public void eventMsg(String message)
    {
    }
    

    
    
    /* begin spawn control stuff */
    
    public Location getHome()
    {
        return _home;
    }

    public void setHome(Location home)
    {
        _home = home;
    }

    public int getMaxHomeDist()
    {
        return _maxHomeDist;
    }

    public void setMaxHomeDist(int maxHomeDist)
    {
        _maxHomeDist = maxHomeDist;
    }
    
    public void settleTo(Location loc, int homeDist)
    {
        moveTo(loc);
        setHome(loc);
        setMaxHomeDist(homeDist);
    }


    public final void processKill(Mobile victim)
    {
        onKill(victim);
    }



    /**
     * Overrideable hook for subclasses.
     * I have killed someone.
     */
    protected void onKill(Mobile victim)
    {
    }

    /* begin overrideable event hook stuff */

    /**
     * Overrideable hook for subclasses.
     * @return 0 this mobile dies.
     */
    protected int onDeath(Entity killer)
    {
        return 0;
    }

    /**
     * Overrideable hook for subclasses.
     */
    protected int onWalk(Location targetLoc)
    {
        return 0;
    }

    public boolean canWalk()
    {
        if (isWalkCooldownActive()) {
            return false;
        }
        
        if (!isPlayerControlled() && isMeleeCooldownActive()) {
            return false;
        }

        if (isFlagSet(FLAG_FROZEN)) {
            return false;
        }
        
        if (isStumbling()) {
            return false;
        }
        
        return true;
    }
    
    public boolean canMelee()
    {
        return canWalk();
    }

    public int getLastWalkDirection()
    {
        return _lastWalkDirection;
    }

    /**
     * Regenerates hitpoints.
     * @return the amount of hits actually regenerated, which can be less
     * then the requested regeneration.
     */
    public int regenerateHitpoints(int hits)
    {
        int actuallyRegged = hits;
        if (_hitpoints + hits > _maxHitpoints) {
            actuallyRegged = _maxHitpoints - _hitpoints; 
        }
        
        _hitpoints += actuallyRegged;

        return actuallyRegged;
    }

    /**
     * Regenerates mana.
     * @return the amount of mana actually regenerated, which can be less
     * then the requested regeneration.
     */
    public int regenerateMana(int mana)
    {
        int actuallyRegged = mana;
        if (_mana + mana > _maxMana) {
            actuallyRegged = _maxMana - _mana; 
        }
        
        _mana += actuallyRegged;

        return actuallyRegged;
    }

    /* melee damage */

    public void setBaseMeleeDamage(Range range)
    {
        _baseMeleeDamage.set(range.getMin(), range.getMax());
    }
    
    public int getBaseMeleeDamageMin()
    {
        return _baseMeleeDamage.getMin();
    }
    
    public int getBaseMeleeDamageMax()
    {
        return _baseMeleeDamage.getMax();
    }

    
    public void setMeleeDamageMod(int mod)
    {
        _meleeDamageMod = mod;
    }
    
    public int getMeleeDamageMod()
    {
        return _meleeDamageMod;
    }
    
    public final int getMeleeDamageMin()
    {
        int min = getBaseMeleeDamageMin() + getMeleeDamageMod(); 
        return min > 0 ? min : 1; //at least 1 damage
    }
    
    public final int getMeleeDamageMax()
    {
        int max = getBaseMeleeDamageMax() + getMeleeDamageMod();
        return max > 0 ? max : 1; //at least 1 damage
    }

    /* armor class */
    
    public void setBaseArmorClass(int armorClass)
    {
        _baseArmorClass = armorClass;
    }

    public int getBaseArmorClass()
    {
        return _baseArmorClass;
    }
    
    public void setArmorClassMod(int mod)
    {
        _armorClassMod = mod;
    }
    
    public int getArmorClassMod()
    {
        return _armorClassMod;
    }
    
    public final int getArmorClass()
    {
        return getBaseArmorClass() + getArmorClassMod();
    }

    
    /* Thaco */
    public void setBaseTHAC0(int thac0)
    {
        _baseThac0 = thac0;
    }
    
    public int getBaseTHAC0()
    {
        return _baseThac0;
    }
    
    public void setTHAC0Mod(int mod)
    {
        _thac0Mod = mod;
    }

    public int getTHAC0Mod()
    {
        return _thac0Mod;
    }

    public final int getTHAC0()
    {
        return getBaseTHAC0() + getTHAC0Mod();
    }
    



    /* melee attack speed */
    
    public void setBaseMeleeAttackSpeed(int baseMeleeAttackSpeed)
    {
        _baseMeleeAttackSpeed = baseMeleeAttackSpeed;
    }
    
    public int getBaseMeleeAttackSpeed()
    {
        return _baseMeleeAttackSpeed;
    }

    public void setMeleeAttackSpeedMod(int mod)
    {
        _meleeAttackSpeedMod = mod;
    }

    public int getMeleeAttackSpeedMod()
    {
        return _meleeAttackSpeedMod;
    }
    
    public final int getMeleeAttackSpeed()
    {
        return getBaseMeleeAttackSpeed() + getMeleeAttackSpeedMod();
    }

    public void addItemContainerChangeListener(ItemContainerChangeListener listener)
    {
        _backpack.addItemContainerChangeListener(listener);
    }

    public int getCapacity()
    {
        return _backpack.getCapacity();
    }

    public List getBuffs()
    {
        return _buffs;
    }








}
